local utils = {}

local _floor = math.floor
local _ceil  = math.ceil
local _min, _max = math.min, math.max

utils.int = _floor -- works better for pixel coords
utils.max = _max
utils.min = _min

utils.trim  = function(x) return x > 0 and _floor(x) or _ceil(x) end
utils.round = function(x) return x > 0 and _floor(x + 0.5) or _ceil(x - 0.5) end

utils.clamp = function(x, a, b)
	if x < a then return a end
	if x > b then return b end
	return x
end

utils.newbox = function(name, x, y, width, height)
	x, y = (x or 0), (y or 0)
	width, height = (width or 0), (height or 0)
	return {name = name, x = x, y = y, width = width, height = height}
end

utils.setbox = function(box, x, y, width, height)
	box.x, box.y = x, y
	box.width, box.height = width, height
	return box
end

utils.includesPoint = function(box, x, y)
	local diffx, diffy = x - box.x, y - box.y
	return diffx >= 0 and diffx < box.width and
	       diffy >= 0 and diffy < box.height
end

utils.includesBox = function(boxA, boxB)
	local diffx, diffy = boxB.x - boxA.x, boxB.y - boxA.y
	return diffx >= 0 and diffx + boxB.width  < boxA.width and
	       diffy >= 0 and diffy + boxB.height < boxA.height
end

utils.intersectsRect = function(boxA, x, y, width, height)
	local diffx, diffy = x - boxA.x, y - boxA.y
	return diffx + width  > 0 and diffx < boxA.width and
	       diffy + height > 0 and diffy < boxA.height
end

utils.intersectsBox = function(boxA, boxB)
	local diffx, diffy = boxB.x - boxA.x, boxB.y - boxA.y
	return diffx + boxB.width  > 0 and diffx < boxA.width and
	       diffy + boxB.height > 0 and diffy < boxA.height
end

-- this assumes h:[0, 6] & s:[0, 1], l:[0, 1]
utils.hsl2rgb = function(h, s, l, a)
	if s <= 0 then return l, l, l, a end
	local c = 2 * s * ((l > 0.5) and (1 - l) or l)
	local m = l - c / 2
	local r, g, b
	if     h < 1 then b, r, g = 0, c, c * h
	elseif h < 2 then b, g, r = 0, c, c * (2 - h)
	elseif h < 3 then r, g, b = 0, c, c * (h - 2)
	elseif h < 4 then r, b, g = 0, c, c * (4 - h)
	elseif h < 5 then g, b, r = 0, c, c * (h - 4)
	else              g, r, b = 0, c, c * (6 - h)
	end
	return (r + m), (g + m), (b + m), a
end

utils.rgb2hsl = function(r, g, b, a)
	local max, min = _max(r, g, b), _min(r, g, b)
	if max == min then return 0, 0, min, a end
	local d, l, s, h = max - min, max + min
	s = d / (l > 1 and 2 - l or l)
	l = l / 2
	if max == r then
		h = (g - b) / d
		if h < 0 then h = h + 6 end
	elseif max == g then
		h = (b - r) / d + 2
	else
		h = (r - g) / d + 4
	end
	return h, s, l, a
end

--utils.format = string.format

local _sub    = string.sub
local _find   = string.find
local _insert = table.insert

utils.split = function(s, sep)
	local t = {}; local init = 1; local m, n
	sep = sep or '\n'
	while true do
		m, n = _find(s, sep, init, true)
		if m == nil then
			_insert(t, _sub(s, init))
			break
		end
		_insert(t, _sub(s, init, m - 1))
		init = n + 1
	end
	return t, #t
end

local deepcopy
deepcopy = function(t, mem)
	if type(t) ~= 'table' then return t end
	local copy = mem[t]
	if copy then return copy end
	copy = {}
	mem[t] = copy
	local meta = getmetatable(t)
	for k, v in pairs(t) do
		k = deepcopy(k, mem)
		v = deepcopy(v, mem)
		copy[k] = v
	end
	setmetatable(copy, meta)
	return copy
end
utils.deepcopy = function(t) return deepcopy(t, {}) end

local ids = {}
utils.genid = function(name)
	local id = (ids[name] or 0) + 1
	ids[name] = id
	return ("%s_%i"):format(name, id)
end

local typeof
typeof = function(class, typename)
	if not class then return false end
	if class.name == typename then return true end
	return typeof(class.parent, typename)
end

local default_object = {
	name = "boxuiObject",
	["type"] = function(object) return object.class.name end,
	["typeOf"] = function(object, typename) return typeof(object.class, typename) end,
}
default_object.__index = default_object

utils.newclass = function(name, parent)
	parent = parent or default_object
	local class = utils.deepcopy(parent)
	class.name = name
	class.parent = parent
	class.new = function(...) 
		local object = setmetatable({}, class)
		object.class = class
		object.name = utils.genid(class.name)
		class.init(object, ...)
		return object
	end -- new
	
	return class
end

-- add or update
utils.addprops = function(t, props)
	for k, v in pairs(props) do
		t[k] = v
	end
	return t
end

-- update existing
utils.freshenprops = function(t, props)
	for k, v in pairs(props) do
		if t[k] ~= nil then t[k] = v end
	end
	return t
end

return utils

